﻿using System;
using System.Drawing;
using System.Windows.Forms;

namespace TodoSoft.Controls
{
	public partial class ScrollPanel : Control
	{
		private Size _scrollArea;
		private readonly SolidBrush _backColorSolidBrush = new SolidBrush(Color.White);
		private Bitmap _cachedBitmap = new Bitmap(1, 1);
		private Rectangle _cachedBitmapArea = new Rectangle(0, 0, 0, 0);
		private int _visibleWidth;
		private int _visibleHeight;

		public ScrollPanel()
		{
			InitializeComponent();
			ScrollArea = new Size(0, 0);
		}

		private void SetScrollBarsVisible(bool horizontal, bool vertical)
		{
			_vScrollBar.Visible = vertical;
			_padPanel.Visible = vertical && horizontal;
			_hScrolPanel.Visible = horizontal;
		}

		public int VScrollBarWidth
		{
			set 
			{
				_vScrollBar.Width = value;
				_padPanel.Width = value;
			}
			get { return _vScrollBar.Width; }
		}

		protected override void OnResize(EventArgs e)
		{
			SetScrollArea(ScrollArea.Width, ScrollArea.Height);
			
			base.OnResize(e);
		}

        public int HScrollBarHeight
		{
			set { _hScrolPanel.Height = value; }
			get { return _hScrolPanel.Height; }
		}

		public Size ScrollArea
		{
			set
			{
				_scrollArea = value;
				SetScrollArea(_scrollArea.Width, _scrollArea.Height);
			}
			get { return _scrollArea; }
		}

		public void SetScrollArea(int width, int height)
		{
			var visibleWidth = Width;
			var visibleHeight = Height;

			var vScrollBarVisible = height > visibleHeight;
			if (vScrollBarVisible)
				visibleWidth -= VScrollBarWidth;

			var hScrollBarVisible = width > visibleWidth;
			if (hScrollBarVisible)
			{
				visibleHeight -= HScrollBarHeight;
				vScrollBarVisible = height > visibleHeight;
			}

			if(vScrollBarVisible)
				_vScrollBar.Maximum = height - visibleHeight + _vScrollBar.LargeChange;

			if(hScrollBarVisible)
				_hScrollBar.Maximum = width - visibleWidth + _hScrollBar.LargeChange;

			_visibleHeight = visibleHeight;
			_visibleWidth = visibleWidth;
			SetScrollBarsVisible(hScrollBarVisible, vScrollBarVisible);

			// Reset cache
			_cachedBitmap = new Bitmap(1, 1);
			_cachedBitmapArea = new Rectangle(0, 0, 0, 0);

			_vScrollBar.Value = Math.Min(ScrollArea.Height, _vScrollBar.Value);
			_hScrollBar.Value = Math.Min(ScrollArea.Width, _hScrollBar.Value);
		}

		private void OnVSrollBarChanged(object sender, EventArgs e)
		{
			Invalidate();
		}

		private void OnHScrollBarChanged(object sender, EventArgs e)
		{
			Invalidate();
		}

		public Rectangle VisibleArea
		{
			get
			{
				return new Rectangle(_hScrollBar.Value, _vScrollBar.Value, _visibleWidth, _visibleHeight);
			}
		}

		protected override void OnPaint(PaintEventArgs e)
		{
			var fullVisibleArea = VisibleArea;
			if (fullVisibleArea.Width == 0 || fullVisibleArea.Height == 0)
				return;

			var cachedBitmap = new Bitmap(fullVisibleArea.Width, fullVisibleArea.Height);

            using (var graphics = Graphics.FromImage(cachedBitmap))
            {
				// Draw cached image
				if (_cachedBitmapArea.Width > 0 && _cachedBitmapArea.Height > 0 &&
					fullVisibleArea.Bottom > _cachedBitmapArea.Y &&
					fullVisibleArea.Right > _cachedBitmapArea.X &&
					_cachedBitmapArea.Bottom > fullVisibleArea.Y &&
					_cachedBitmapArea.Right > fullVisibleArea.X)
				{
					graphics.DrawImage(
						_cachedBitmap, 
						_cachedBitmapArea.X - fullVisibleArea.X,
						_cachedBitmapArea.Y - fullVisibleArea.Y);
				}

				graphics.Translate = new Point(-fullVisibleArea.X, - fullVisibleArea.Y);

				// Draw new opened areas
	            var newAreaHeightAtTop = _cachedBitmapArea.Y - fullVisibleArea.Y;
				if (newAreaHeightAtTop > 0)
				{
					var rectangle = new Rectangle(
						fullVisibleArea.X, fullVisibleArea.Y, fullVisibleArea.Width, newAreaHeightAtTop);

					PaintContent(graphics, rectangle);
				}

	            var newAreaHeightAtBottom = fullVisibleArea.Bottom - _cachedBitmapArea.Bottom;
				if (newAreaHeightAtBottom > 0)
				{
					var rectangle = new Rectangle(
						fullVisibleArea.X, _cachedBitmapArea.Bottom, fullVisibleArea.Width, newAreaHeightAtBottom);

					PaintContent(graphics, rectangle);
				}

	            var newAreaWidthAtLeft = _cachedBitmapArea.X - fullVisibleArea.X;
				if (newAreaWidthAtLeft > 0)
				{
					var rectangle = new Rectangle(
						fullVisibleArea.X, Math.Max(fullVisibleArea.Y, _cachedBitmapArea.Y),
						newAreaWidthAtLeft, 
						Math.Min(fullVisibleArea.Bottom, _cachedBitmapArea.Bottom) - 
						Math.Max(fullVisibleArea.Y, _cachedBitmapArea.Y));

					PaintContent(graphics, rectangle);
				}

	            var newAreaWidthAtRight = fullVisibleArea.Right - _cachedBitmapArea.Right;
				if (newAreaWidthAtRight > 0)
				{
					var rectangle = new Rectangle(
						_cachedBitmapArea.Right, Math.Max(fullVisibleArea.Y, _cachedBitmapArea.Y),
						newAreaWidthAtRight,
						Math.Min(fullVisibleArea.Bottom, _cachedBitmapArea.Bottom) - 
						Math.Max(fullVisibleArea.Y, _cachedBitmapArea.Y));
					
					if(rectangle.Width > 0 && rectangle.Height > 0)
                        PaintContent(graphics, rectangle);
				}
            }
		
			e.Graphics.DrawImage(cachedBitmap, 0, 0);

			_cachedBitmap.Dispose();
			_cachedBitmap = cachedBitmap;
			_cachedBitmapArea = fullVisibleArea;
		}

		protected override void OnPaintBackground(PaintEventArgs e)
		{
			// Suppress flickering.
		}

		public override Color BackColor
		{
			get
			{
				return base.BackColor;
			}
			set
			{
				_backColorSolidBrush.Color = value;
				base.BackColor = value;
			}
		}

		protected virtual void PaintContent(Graphics graphics, Rectangle clipRectangle)
		{
			graphics.FillRectangle(_backColorSolidBrush, clipRectangle);
		}
	}
}
